import requests
import json
import base64
import mimetypes
import os
from typing import Optional, List, Dict, Union
from .token_store import TokenStore

AUTH_URL = "https://login.microsoftonline.com/common/oauth2/v2.0/authorize"
TOKEN_URL = "https://login.microsoftonline.com/common/oauth2/v2.0/token"
GRAPH_URL = "https://graph.microsoft.com/v1.0"
CLIENT_ID = "acf0a6ea-e76d-4550-b031-b9bca70cf07d"
SCOPES = "offline_access user.read mail.read mail.send"
TIMEOUT_SEC = 30

class GraphClient:
    def __init__(self, redirect_uri: str, store: TokenStore):
        self.redirect_uri = redirect_uri
        self.store = store
        self.access_token, self.refresh_token = self.store.get_tokens()

    def authorization_url(self) -> str:
        return (
            f"{AUTH_URL}?client_id={CLIENT_ID}&response_type=code&redirect_uri="
            f"{self.redirect_uri}&response_mode=query&scope={SCOPES.replace(' ', '+')}&state=12345"
        )

    def verify_token(self) -> bool:
        if not self.access_token:
            return False
        headers = {"Authorization": f"Bearer {self.access_token}", "Content-Type": "application/json"}
        try:
            r = requests.get(f"{GRAPH_URL}/me", headers=headers, timeout=TIMEOUT_SEC)
            return r.status_code == 200
        except Exception:
            return False

    def refresh_access_token(self) -> bool:
        if not self.refresh_token:
            return False
        data = {
            "client_id": CLIENT_ID,
            "scope": SCOPES,
            "refresh_token": self.refresh_token,
            "grant_type": "refresh_token",
            "redirect_uri": self.redirect_uri,
        }
        headers = {"Content-Type": "application/x-www-form-urlencoded"}
        try:
            r = requests.post(TOKEN_URL, data=data, headers=headers)
            r.raise_for_status()
            j = r.json()
            self.access_token = j.get("access_token")
            new_refresh = j.get("refresh_token")
            if new_refresh:
                self.refresh_token = new_refresh
            if self.access_token:
                self.store.save_tokens(self.access_token, self.refresh_token)
                return True
            return False
        except Exception:
            return False

    def exchange_code_for_token(self, code: str) -> bool:
        data = {
            "client_id": CLIENT_ID,
            "scope": SCOPES,
            "code": code,
            "redirect_uri": self.redirect_uri,
            "grant_type": "authorization_code",
        }
        headers = {"Content-Type": "application/x-www-form-urlencoded"}
        try:
            r = requests.post(TOKEN_URL, data=data, headers=headers, timeout=TIMEOUT_SEC)
            r.raise_for_status()
            j = r.json()
            self.access_token = j.get("access_token")
            self.refresh_token = j.get("refresh_token")
            if self.access_token:
                self.store.save_tokens(self.access_token, self.refresh_token)
                return True
            return False
        except Exception:
            return False

    def send_email(self, to_email: Union[str, List[str]], subject: str, body: str, body_type: str = "Text", from_email: Optional[str] = None, attachments: Optional[List[Dict]] = None, cc: Optional[List[str]] = None, bcc: Optional[List[str]] = None) -> tuple[bool, Optional[str]]:
        """
        发送邮件，可选携带附件与多收件人；附件支持：
        1) Graph 附件对象
        2) 表单构造：name/contentType/contentBytes
        3) 旧格式：path 或 content_base64
        """
        if not self.access_token:
            return False, None
        headers = {"Authorization": f"Bearer {self.access_token}", "Content-Type": "application/json; charset=utf-8"}
        recips: List[str] = [to_email] if isinstance(to_email, str) else list(to_email or [])
        payload = {
            "message": {
                "subject": subject,
                "body": {"contentType": body_type, "content": body},
                "toRecipients": [{"emailAddress": {"address": addr}} for addr in recips if addr],
            }
        }
        if cc:
            payload["message"]["ccRecipients"] = [{"emailAddress": {"address": addr}} for addr in cc if addr]
        if bcc:
            payload["message"]["bccRecipients"] = [{"emailAddress": {"address": addr}} for addr in bcc if addr]
        if from_email:
            payload["message"]["from"] = {"emailAddress": {"address": from_email}}

        att_list: List[Dict] = []
        if attachments:
            for att in attachments:
                if not isinstance(att, dict):
                    return False, "attachment_invalid"
                # 1) 已构造 Graph 附件或 2) 表单构造
                if att.get("@odata.type") or (att.get("name") and (att.get("contentBytes") or att.get("content_base64"))):
                    name = att.get("name")
                    ctype = att.get("contentType") or att.get("content_type") or "application/octet-stream"
                    content_b64 = att.get("contentBytes") or att.get("content_base64")
                    if not (name and content_b64):
                        return False, "attachment_invalid"
                    att_list.append({
                        "@odata.type": att.get("@odata.type") or "#microsoft.graph.fileAttachment",
                        "name": name,
                        "contentType": ctype,
                        "contentBytes": content_b64,
                    })
                    continue
                # 3) 旧格式：path
                path = att.get("path")
                if path:
                    try:
                        with open(path, "rb") as f:
                            raw = f.read()
                        name = att.get("name") or os.path.basename(path)
                        guessed, _ = mimetypes.guess_type(path)
                        ctype = att.get("content_type") or guessed or "application/octet-stream"
                        content_b64 = base64.b64encode(raw).decode("ascii")
                        att_list.append({
                            "@odata.type": "#microsoft.graph.fileAttachment",
                            "name": name,
                            "contentType": ctype,
                            "contentBytes": content_b64,
                        })
                    except Exception as e:
                        return False, f"attachment_read_error:{path}:{e}"
                else:
                    return False, "attachment_invalid"
        if att_list:
            payload["message"]["attachments"] = att_list

        try:
            data = json.dumps(payload, ensure_ascii=False)
            r = requests.post(f"{GRAPH_URL}/me/sendMail", headers=headers, data=data, timeout=TIMEOUT_SEC)
            r.raise_for_status()
            req_id = r.headers.get("request-id") or r.headers.get("client-request-id")
            return True, req_id
        except requests.exceptions.RequestException as e:
            return False, getattr(e.response, "text", str(e))

    def get_sent_messages(self, count: int = 5):
        if not self.access_token:
            return []
        headers = {"Authorization": f"Bearer {self.access_token}", "Content-Type": "application/json"}
        params = {"$top": count, "$select": "subject,toRecipients,sentDateTime,bodyPreview", "$orderby": "sentDateTime desc"}
        try:
            r = requests.get(f"{GRAPH_URL}/me/mailFolders/sentitems/messages", headers=headers, params=params, timeout=TIMEOUT_SEC)
            r.raise_for_status()
            j = r.json()
            return j.get("value", [])
        except requests.exceptions.RequestException:
            return []

    def get_inbox_messages(self, count: int = 5):
        """
        获取收件箱邮件，返回最近的 count 封邮件
        """
        if not self.access_token:
            return []
        headers = {"Authorization": f"Bearer {self.access_token}", "Content-Type": "application/json"}
        params = {
            "$top": count,
            "$select": "subject,from,receivedDateTime,bodyPreview,isRead",
            "$orderby": "receivedDateTime desc",
        }
        try:
            r = requests.get(f"{GRAPH_URL}/me/mailFolders/inbox/messages", headers=headers, params=params, timeout=TIMEOUT_SEC)
            r.raise_for_status()
            j = r.json()
            return j.get("value", [])
        except requests.exceptions.RequestException:
            return []
